package lrscp.lib.thread;

import java.util.ArrayList;
import java.util.List;

import lrscp.lib.Log;

public abstract class TaskScheduler<I, O> {
    private int threads;
    private I[] inputs;
    private List<O> outputs;
    private boolean[] availableIndicator;
    private Thread[] allThreads;
    private boolean started;
    private boolean inOrder;
    private int lastConsumIndex = 0;
    private int lastAcquireIndex = 0;

    /**
     * After one executing, one task is finished. This method must be able to
     * handle mutil-thread accessing situations, so you'd better write a
     * reenterable method and use non-global variables
     * 
     * @param input
     * @return output
     */
    protected abstract O execute(I input);

    /**
     * don't do much works in this method, it has been synchronized.
     * 
     * @param output
     */
    protected abstract void onFinishOne(O output);

    public TaskScheduler(int threads, boolean inOrder, I[] inputs, ArrayList<O> outputs)
            throws Exception {
        if (threads < 1)
            threads = 1;
        if (inputs.length != outputs.size()) {
            throw new Exception("inputs.length != outputs.length");
        }
        this.threads = threads;
        this.allThreads = new Thread[threads];
        this.inputs = inputs;
        this.outputs = outputs;
        this.availableIndicator = new boolean[inputs.length];
        this.inOrder = inOrder;
    }

    public synchronized void start() {
        if (started)
            return;
        for (int i = 0; i < threads; i++) {
            startThread(i);
        }
        started = true;
    }

    public synchronized void forceStop() {
        for (Thread thd : allThreads) {
            try {
                thd.stop();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private synchronized void threadExit(int id) {
        threads--;
        allThreads[id] = null;
    }

    private synchronized void startThread(final int id) {
        allThreads[id] = new Thread(new Runnable() {
            @Override
            public void run() {
                int index = acquireInput();
                if (index < 0) {
                    Log.i("no more task, exits:" + Thread.currentThread().getName());
                    threadExit(id);
                    return;
                }
                outputs.set(index, execute(inputs[index]));
                finishOne(index);
                startThread(id);
            }
        }, "thread-" + id);
        allThreads[id].start();
    }

    private synchronized int acquireInput() {
        if (lastAcquireIndex < inputs.length)
            return lastAcquireIndex++;
        return -1;
    }

    private synchronized boolean isAllFinished() {
        boolean ret = true;
        for (boolean b : availableIndicator) {
            if (b == false) {
                ret = false;
                break;
            }
        }
        return ret;
    }

    private synchronized void finishOne(int index) {
        availableIndicator[index] = true;
        if (!inOrder)
            onFinishOne(outputs.get(index));
        else {
            for (int i = lastConsumIndex; i < inputs.length; i++) {
                if (availableIndicator[i]) {
                    onFinishOne(outputs.get(index));
                } else {
                    lastConsumIndex = i;
                    break;
                }
            }
        }
        if (isAllFinished()) {
            allFinished();
            notifyAll();
        }
    }

    public void waitForAllFinish() {
        synchronized (this) {
            while (!isAllFinished()) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * called when all task points have been done.
     */
    protected abstract void allFinished();
}
